﻿using CsvHelper;
using CsvHelper.Configuration;
using F23.StringSimilarity;
using Microsoft.Extensions.Configuration;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Yoko.Excel.Core;
using Yoko.Tool.Core;
using 表格分类;
using 表格分类.map;

Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("======= 开始读取数据表 =======");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();

var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

IConfigurationRoot configuration = builder.Build();

// 脏单阈值
int DirtyOrderCount = configuration.GetSection("AppSettings:DirtyOrderCount").Value.ToInt32();
// 需要检查的物品列表
List<string> itemsToCheck = configuration.GetSection("AppSettings:ItemsToCheck").Get<List<string>>();
// 需要检查的物品列表
List<string> itemsToSkip = configuration.GetSection("AppSettings:ItemsToSkip").Get<List<string>>();

var dateInFileName = "";

// 创建Jaccard对象
var jaccard = new Jaccard();

// 创建一个新的List<DataModel>来存储所有的数据
List<DataModel> allFuzhuang = [];
List<DataModel> allBaihuo = [];
// 创建一个新的List<DataModel>来存储地址不全的数据
List<DataModel> incompleteAddressBaihuo = [];
// 创建一个新的List<DataModel>来存储标题是"顺丰包邮"且"收件地址"是新疆、西藏的数据
List<DataModel> specialFuzhuang = [];

//注册编码
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

// 遍历所有的文件
string currentDirectory = Path.Combine(Directory.GetCurrentDirectory(), "docs");
if (!Directory.Exists(currentDirectory))
{
    Directory.CreateDirectory(currentDirectory);
}
var files = currentDirectory.GetFilePath();
if (files.Count > 0)
{
    var timepath = Path.Combine(Directory.GetCurrentDirectory(), "分类结果_" + DateTime.Now.ToString("yyyy-MM-dd HHmmss"));
    Directory.CreateDirectory(timepath);

    foreach (var url in files)
    {
        //FileInfo fileInfo = new FileInfo(url);
        //var encoding = EncodingDifferentiater.DifferentiateEncoding(fileInfo);
        //var encodingName = encoding.encoding.BodyName;

        //var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        //{
        //    PrepareHeaderForMatch = args => args.Header.ToLower(),
        //    Encoding = Encoding.GetEncoding("GB2312"),
        //};

        Console.WriteLine(url);
        var fileName = Path.GetFileName(url);
        // 提取文件名中的日期
        var dateMatch = Regex.Match(fileName, @"\d{4}-\d{2}-\d{2}");
        if (!dateMatch.Success)
        {
            Console.WriteLine("无法从文件名中提取日期");
            continue;
        }
        dateInFileName = dateMatch.Value;

        //读取csv文件
        using var reader = new StreamReader(url, encoding: Encoding.GetEncoding("GB2312"));
        var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            BadDataFound = context =>
            {
                // 在遇到不合法数据时记录日志
                Console.WriteLine("");
                Console.WriteLine("===========  检测到不合法数据行  [已忽略该行] ============");
                Console.WriteLine("");
                Console.WriteLine($"{context.RawRecord}");
                Console.WriteLine("");
            },
            MissingFieldFound = null // 遇到缺少字段时忽略
        };
        using var csv = new CsvReader(reader, csvConfig);
        //using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
        csv.Context.RegisterClassMap<DataModelMap>();
        var data = csv.GetRecords<DataModel>().ToList();

        // 读取excel文件
        //var data = YokoExcel.Query<DataModel>(url).ToList();

        // 根据电话号码 去重，并验证电话号码 如果下单时间、产品名不同，就不需要去重
        data = data.Where(x => x.联系方式.Trim().IsMobile())
            .GroupBy(x => new { x.联系方式, x.下单时间, x.产品名 })
            .Select(g => g.First())
            .ToList();
        //Console.WriteLine("根据电话号码 去重，并验证电话号码后，剩余：" + data.Count);

        // 源文件类型，普通表还是额外表
        var type = fileName.Contains("普通") ? "普通" : "额外";

        // 普通表
        //if (fileName.Contains("普通"))
        //{
        //    var datapath = Path.Combine(Directory.GetCurrentDirectory(), $"{dateInFileName}_{type}_{data.Count}单.xlsx");
        //    YokoExcel.SaveAs(datapath, data, sheetName: $"{type}_{data.Count}单", overwriteFile: true);
        //}

        // 【额外表】需要删除的
        if (fileName.Contains("额外"))
        {
            // 删除脏单数 > 40 的数据
            data = data.Where(x => x.脏单数.ToInt32() <= DirtyOrderCount).ToList();

            // 导出
            //var datapath = Path.Combine(Directory.GetCurrentDirectory(), $"{dateInFileName}_额外_{data.Count}单.xlsx");
            //YokoExcel.SaveAs(datapath, data, sheetName: $"额外_{data.Count}单", overwriteFile: true);
        }

        // 筛选出【服装】的表格 条件：根据 "标题" 筛选，包含 "顺丰包邮 质量保证" ，且"地址"，不能包含"新疆"、"西藏"
        var fuzhuang = data.Where(x => !string.IsNullOrWhiteSpace(x.收件地址))
            .Where(x => x.标题.Trim().Contains("顺丰包邮"))
            .Where(x => !x.收件地址.Trim().Contains("新疆") && !x.收件地址.Trim().Contains("西藏"))
            .ToList();

        if (fileName.Contains("额外"))
        {
            //【针对服装】 删除"套餐"码数(S、L、M)不全的数据
            List<string> sizes = ["S", "M", "L"];

            //// 过滤包含itemsToCheck中任意一个项目且套餐不包含sizes中任意一个的记录
            //fuzhuang = fuzhuang.Where(x => !string.IsNullOrWhiteSpace(x.产品名) && !string.IsNullOrWhiteSpace(x.套餐))
            //                   .Where(x => !itemsToCheck.Any(item => x.产品名.Contains(item) && !sizes.Any(size => x.套餐.Contains(size))))
            //                   .ToList();

            // 针对服装，删除"套餐"码数(S、L、M)不全的数据
            fuzhuang = fuzhuang
                .Where(x => !string.IsNullOrWhiteSpace(x.产品名) && !string.IsNullOrWhiteSpace(x.套餐))
                .Where(x =>
                    // 如果产品名中包含 itemsToSkip 中的任意关键词，则直接跳过此项，不进行删除
                    itemsToSkip.Any(skip => x.产品名.Contains(skip)) ||
                    // 否则，检查 itemsToCheck 和 sizes 是否匹配
                    !itemsToCheck.Any(item => x.产品名.Contains(item) &&
                        !sizes.Any(size => x.套餐.Contains(size))
                    )
                )
                .ToList();
        }
        //Console.WriteLine(type + "表_服装：" + fuzhuang.Count);

        // 导出【服装】的表格
        //var fuhzhuangpath = Path.Combine(Directory.GetCurrentDirectory(), $"{dateInFileName}_{type}_服装_{fuzhuang.Count}单.xlsx");
        //YokoExcel.SaveAs(fuhzhuangpath, fuzhuang, sheetName: $"{type}_服装_{fuzhuang.Count}单", overwriteFile: true);

        // 将标题是"顺丰包邮"且"收件地址"是新疆、西藏的数据单独保存到specialFuzhuang
        specialFuzhuang = data.Where(x => !string.IsNullOrWhiteSpace(x.收件地址))
            .Where(x => x.标题.Trim().Contains("顺丰包邮"))
            .Where(x => x.收件地址.Trim().Contains("新疆") || x.收件地址.Trim().Contains("西藏"))
            .ToList();
        if (specialFuzhuang.Count > 0)
        {
            // 导出【特殊服装】的表格
            YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_{type}_服装（新疆西藏）_{specialFuzhuang.Count}单.xlsx"),
                specialFuzhuang,
                sheetName: $"{type}_特殊服装_{specialFuzhuang.Count}单",
                overwriteFile: true);
            Console.WriteLine($"导出成功!   {dateInFileName}_{type}_服装（新疆西藏）_{specialFuzhuang.Count}单.xlsx");
        }

        // 【求差集】剩下的数据，都是【百货】
        var baihuo = data.Except(fuzhuang).ToList();
        // 在【百货】中，不能有服装单，收件地址可以为空，如果不为空，就不能包含"西藏"
        baihuo = baihuo.Where(x => !x.标题.Trim().Contains("顺丰包邮"))
            .Where(x => string.IsNullOrWhiteSpace(x.收件地址) || !x.收件地址.Trim().Contains("西藏")).ToList();
        //Console.WriteLine(type + "表_百货：" + baihuo.Count);

        // 导出
        //var baihuopath = Path.Combine(Directory.GetCurrentDirectory(), $"{dateInFileName}_{type}_百货_{baihuo.Count}单.xlsx");
        //YokoExcel.SaveAs(baihuopath, baihuo, sheetName: $"{type}_百货_{baihuo.Count}单", overwriteFile: true);

        // 将"服装"和"百货"的数据添加到allFuzhuang和allBaihuo
        allFuzhuang = [.. allFuzhuang, .. fuzhuang];
        allBaihuo = [.. allBaihuo, .. baihuo];
    }

    // 根据电话号码、下单时间和产品名去重
    allFuzhuang = allFuzhuang.GroupBy(x => new { x.联系方式, x.标题, x.套餐 })
        .Select(g => g.First())
        .ToList();

    allBaihuo = allBaihuo.GroupBy(x => new { x.联系方式, x.标题, x.套餐 })
        .Select(g => g.First())
        .ToList();

    // 导出合并后的数据
    YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_服装（未过滤）_{allFuzhuang.Count}单.xlsx"),
        allFuzhuang,
        sheetName: $"合并_服装（未过滤）_{allFuzhuang.Count}单",
        overwriteFile: true);
    Console.WriteLine($"导出成功!  {dateInFileName}_合并_服装（未过滤）_{allFuzhuang.Count}单.xlsx");
    YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_百货（未过滤）_{allBaihuo.Count}单.xlsx"),
        allBaihuo,
        sheetName: $"合并_百货（未过滤）_{allBaihuo.Count}单",
        overwriteFile: true);
    Console.WriteLine($"导出成功!   {dateInFileName}_合并_百货（未过滤）_{allBaihuo.Count}单.xlsx");
    //1、在合并后服装表中，收货地址不全的，直接删掉
    //2、在合并后百货表中，【额外】表的，收货地址不全的数据，单独保存到一个文件

    // 获取所有街道地址中出现频率最高的一组字
    var allStreets = allFuzhuang
        .Where(x => !string.IsNullOrWhiteSpace(x.收件地址))
        .Select(x => x.收件地址.Split('|').Last().Trim()).ToList();

    var excludedChars = new HashSet<char> { '省', '县', '市', '区' };

    var top40Chars = allStreets
        .SelectMany(s => s) // 将所有街道地址的字符展开
        .Where(c => Regex.IsMatch(c.ToString(), @"[\u4e00-\u9fa5]") && !excludedChars.Contains(c)) // 只保留汉字字符，并排除特定字符
        .GroupBy(c => c) // 按字符分组
        .Select(g => new { Char = g.Key, Count = g.Count() }) // 计算每个字符的出现次数
        .OrderByDescending(g => g.Count) // 按出现次数降序排序
        .Take(40) // 取出现次数最多的前30个字符
        .Select(g => g.Char) // 取这些字符
        .ToList();

    top40Chars.Add('局');

    // 创建一个包含所有无效地址字符串的集合
    var invalidAddressParts = new HashSet<string> { "联系", "屎" };

    // 【检查收货地址】在合并后服装表中，收货地址不全的，直接删掉
    allFuzhuang = allFuzhuang.Where(x =>
    {
        if (x.订单类型 == "普通")
        {
            return true;
        }

        if (string.IsNullOrWhiteSpace(x.收件地址))
        {
            return false;
        }

        var addressParts = x.收件地址.Trim().Split('|');

        // 从数组的最后一个元素开始，只要元素为空，就删除
        while (addressParts.Length != 0 && string.IsNullOrWhiteSpace(addressParts.Last()))
        {
            addressParts = addressParts.Take(addressParts.Length - 1).ToArray();
        }

        // 检查 收件地址 是否包含无效地址字符串
        if (invalidAddressParts.Any(part => x.收件地址.Contains(part)))
        {
            return false;
        }

        // 如果数组中的空元素数量大于2
        if (addressParts.Length <= 3 || addressParts.Count(part => string.IsNullOrWhiteSpace(part)) >= 2)
        {
            return false;
        }

        if (addressParts.Length > 4)
        {
            var combinedPart = string.Join("|", addressParts.Skip(3));
            addressParts = addressParts.Take(3).Concat([combinedPart]).ToArray();
        }

        // 检查街道地址是否包含非法字符
        var street = addressParts.Last().Trim();

        // 检查街道地址是否包含中文字符
        var chineseCharRegex = new Regex(@"[\u4e00-\u9fa5]"); // 匹配中文字符

        if (!chineseCharRegex.IsMatch(street))
        {
            return false; // 街道地址不包含中文字符，直接删掉
        }

        // 检查街道地址是否包含非法字符
        var illegalCharRegex = new Regex(@"[^a-zA-Z0-9\u4e00-\u9fa5IVXLCDMs\s]"); // 匹配非字母、数字、中文的字符

        // 删除街道地址中的非法字符
        street = illegalCharRegex.Replace(street, "");

        if (street.Length <= 2)
        {
            return false;
        }

        // 检查街道地址长度是否在3到5个字符之间，且是否包含出现频率最高的一组字
        if (street.Length >= 3 && street.Length <= 5 && !top40Chars.Any(c => street.Contains(c)))
        {
            return false;
        }
        return true;
    }).ToList();
    //导出
    YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_服装（初步过滤地址）_发货表_{allFuzhuang.Count}单.xlsx"),
        allFuzhuang,
        sheetName: $"合并_服装（初步过滤地址）_发货表_{allFuzhuang.Count}单",
        overwriteFile: true);
    Console.WriteLine($"导出成功!   {dateInFileName}_合并_服装（初步过滤地址）_发货表_{allFuzhuang.Count}单.xlsx");
    // 根据产品名相似度 > 70%，聚类表格
    Dictionary<string, List<DataModel>> groups = [];
    foreach (var item in allFuzhuang)
    {
        bool foundGroup = false;
        foreach (var group in groups)
        {
            if (jaccard.Similarity(item.产品名, group.Key) >= 0.7)
            {
                group.Value.Add(item);
                foundGroup = true;
                break;
            }
        }
        if (!foundGroup)
        {
            groups[item.产品名] = [item];
        }
    }

    foreach (var group in groups)
    {
        // 导出
        YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_服装（初步过滤地址）_分类_{group.Key}_{group.Value.Count}单.xlsx"),
            group.Value,
            sheetName: $"{group.Value.Count}单",
            overwriteFile: true);
        Console.WriteLine($"导出成功!   {dateInFileName}_合并_服装（初步过滤地址）_分类_{group.Key}_{group.Value.Count}单.xlsx");
    }

    // 【检查收货地址】对于百货表中，额外表的、收货地址不全的数据，单独保存到一个文件
    allBaihuo = allBaihuo.Where(x =>
    {
        if (x.订单类型 == "普通")
        {
            return true;
        }

        if (string.IsNullOrWhiteSpace(x.收件地址))
        {
            incompleteAddressBaihuo.Add(x); // 地址为空或null，添加到新的列表中
            return false;
        }

        //if (x.收件地址.Contains("贵州|六盘水|盘州市|l丶"))
        //{
        //    var ads = "asd";
        //}

        var addressParts = x.收件地址.Trim().Split('|');

        // 从数组的最后一个元素开始，只要元素为空，就删除
        while (addressParts.Length != 0 && string.IsNullOrWhiteSpace(addressParts.Last()))
        {
            addressParts = addressParts.Take(addressParts.Length - 1).ToArray();
        }

        // 检查 收件地址 是否包含无效地址字符串
        if (invalidAddressParts.Any(part => x.收件地址.Contains(part)))
        {
            incompleteAddressBaihuo.Add(x); // 地址包含无效地址字符串，添加到新的列表中
            return false;
        }

        // 如果数组中的空元素数量大于2
        if (addressParts.Length <= 3 || addressParts.Count(part => string.IsNullOrWhiteSpace(part)) >= 2)
        {
            incompleteAddressBaihuo.Add(x); // 地址不全，添加到新的列表中
            return false;
        }

        if (addressParts.Length > 4)
        {
            var combinedPart = string.Join("|", addressParts.Skip(3));
            addressParts = addressParts.Take(3).Concat([combinedPart]).ToArray();
        }

        // 检查街道地址是否包含非法字符
        var street = addressParts.Last().Trim();

        // 检查街道地址是否包含中文字符
        var chineseCharRegex = new Regex(@"[\u4e00-\u9fa5]"); // 匹配中文字符

        if (!chineseCharRegex.IsMatch(street))
        {
            incompleteAddressBaihuo.Add(x);
            return false; // 街道地址不包含中文字符，直接删掉
        }

        // 检查街道地址是否包含非法字符
        var illegalCharRegex = new Regex(@"[^a-zA-Z0-9\u4e00-\u9fa5IVXLCDMs\s]"); // 匹配非字母、数字、中文的字符

        // 删除街道地址中的非法字符
        street = illegalCharRegex.Replace(street, "");

        if (street.Length <= 2)
        {
            incompleteAddressBaihuo.Add(x);
            return false;
        }

        // 检查街道地址长度是否在3到5个字符之间，且是否包含出现频率最高的一组字
        if (street.Length >= 3 && street.Length <= 5 && !top40Chars.Any(c => street.Contains(c)))
        {
            incompleteAddressBaihuo.Add(x); // 街道地址不包含出现频率最高的一组字，添加到新的列表中
            return false;
        }
        return true;
    }).ToList();
    // 导出地址不全的数据
    YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_百货（初步过滤地址）_发货表_{allBaihuo.Count}单.xlsx"),
        allBaihuo,
        sheetName: $"合并_百货（初步过滤地址）_发货表_{allFuzhuang.Count}单",
        overwriteFile: true);
    Console.WriteLine($"导出成功!   {dateInFileName}_合并_百货（初步过滤地址）_发货表_{allBaihuo.Count}单.xlsx");
    YokoExcel.SaveAs(Path.Combine(Directory.GetCurrentDirectory(), timepath, $"{dateInFileName}_合并_百货（初步过滤地址）_地址不全_{incompleteAddressBaihuo.Count}单.xlsx"),
        incompleteAddressBaihuo,
        sheetName: $"合并_百货_地址不全_{incompleteAddressBaihuo.Count}单",
        overwriteFile: true);
    Console.WriteLine($"导出成功!   {dateInFileName}_合并_百货（初步过滤地址）_地址不全_{incompleteAddressBaihuo.Count}单.xlsx");

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine("======= ok =======");
}
else
{
    Console.WriteLine("未检测到csv文件");
}

Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("输入任意键结束程序");
Console.ReadKey();